package net.openvision.music.composition.logical.parsing;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.text.ParseException;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import net.openvision.music.composition.rules.RuleSet;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

public class XmlRuleSetParser extends DefaultHandler implements RuleSetParser {

	public static interface ElementHandler<T> {
		public void startElement(Attributes attributes);

		public T endElement();

		public void characters(char ch[], int start, int length);
	}

	private List<RuleSet> _ruleSets;
	private ElementHandler<?> _currentHandler;
	private List<Object> _objects;
	private Deque<Object> _openElements;

	private Map<String, Class<?>> typeMapping;
	private Map<String, Class<?>> defaultType;

	private static final String TYPE_ATTR = "type";
	private static final String RULESET_ID = "ruleset";

	private String getBeanGetter(String propertyName) {
		return "get" + propertyName.substring(0, 1).toUpperCase()
				+ propertyName.substring(1);
	}
	
	private String getBeanSetter(String propertyName) {
		return "set" + propertyName.substring(0, 1).toUpperCase()
				+ propertyName.substring(1);
	}

	private <T> T setupElement(Class<T> clazz, Attributes attributes)
			throws SAXException {
		try {
			T element = clazz.newInstance();
			for (int i = 0; i < attributes.getLength(); i++) {
				String methodName = getBeanSetter(attributes.getLocalName(i));
				for (Method m : clazz.getMethods()) {
					if (m.getName().equals(methodName)) {
						Class<?>[] params = m.getParameterTypes();
						if (params.length == 1) {
							Class<?> p = params[0];
							String val = attributes.getValue(i);
							if (p.equals(String.class)) {
								m.invoke(element, val);
								continue;
							} else {
								Method conv = p.getMethod("valueOf",
										String.class);
								if (conv != null
										&& ((conv.getModifiers() & Modifier.STATIC) > 0)) {
									m.invoke(element, conv.invoke(null, val));
									continue;
								} else {
									Constructor<?> c = p
											.getConstructor(String.class);
									m.invoke(element, c.newInstance(val));
									continue;
								}
							}
						}
					}
					throw new SAXException("Attribute "
							+ attributes.getLocalName(i)
							+ " not applicable to class " + clazz.getName());
				}
			}
			return element;
		} catch (InstantiationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (SecurityException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (NoSuchMethodException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return null;
	}

	public XmlRuleSetParser() {
		typeMapping = new HashMap<String, Class<?>>();
		typeMapping.put(RULESET_ID, RuleSet.class);
	}

	public void startElement(String uri, String localName, String qName,
			Attributes attributes) throws SAXException {
		Class<?> c = typeMapping.get(localName);
		String type = attributes.getValue(null, TYPE_ATTR);
		if (type != null) {
			try {
				c = Class.forName(type);
			} catch (ClassNotFoundException e) {
				throw new SAXException("Type " + type + " not found.", e);
			}
		} else {
			if (c.isInterface() || (c.getModifiers() & Modifier.ABSTRACT) > 0) {
				c = defaultType.get(localName);
			}
		}
		_openElements.addLast(setupElement(c, attributes));
	}

	public void endElement(String uri, String localName, String qName)
			throws SAXException {
		Object o = _openElements.pollLast();
		Class<?> c = typeMapping.get(localName);
		for (Object obj : _objects) {
			Method m = null;
			try {
				m = c.getMethod("add", obj.getClass());
			} catch (SecurityException e) {
				e.printStackTrace();
			} catch (NoSuchMethodException e) {
				try {
					m = c.getMethod(getBeanSetter(localName), obj.getClass());
				} catch (SecurityException e1) {
					e1.printStackTrace();
				} catch (NoSuchMethodException e1) {
					continue;
				}
			}
			try {
				m.invoke(o, obj);
			} catch (IllegalArgumentException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (InvocationTargetException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		_objects.add(o);
	}

	public void characters(char ch[], int start, int length)
			throws SAXException {
		
	}

	@Override
	public RuleSet[] parse(InputStream input) throws IOException,
			ParseException, IllegalAccessException, InstantiationException {
		try {
			SAXParserFactory spf = SAXParserFactory.newInstance();
			SAXParser parser = spf.newSAXParser();
			_ruleSets = new ArrayList<RuleSet>();
			_objects = new LinkedList<Object>();
			_openElements = new ArrayDeque<Object>();
			parser.parse(input, this);
			return _ruleSets.toArray(new RuleSet[_ruleSets.size()]);
		} catch (ParserConfigurationException e) {
			throw new IOException("Error in ParseConfiguration", e);
		} catch (SAXException e) {
			throw new IOException("SaxException", e);
		}

	}

}
